package com.otom.bcel;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

public class FieldMappingValidator {
	public void validate(List<FieldMapping> fieldMappings, boolean biDirectional) {
		List<String> errors = new ArrayList<String>();

		for (FieldMapping fieldMapping : fieldMappings) {
			errors.addAll(validate(fieldMapping, biDirectional));
		}

		if (!errors.isEmpty()) {
			throw new ValidationException(errors);
		}
	}

	public List<String> validate(FieldMapping fieldMapping, boolean biDirectional) {
		List<String> errors = new ArrayList<String>();
		FieldInfo source = fieldMapping.getSource();
		
		validateFieldInfo(source, biDirectional, true, errors);
		validateGetMethod(source, errors);
		validateSetMethod(source, errors);

		validateCollectionTypeInfo(source, errors);

		FieldInfo destination = fieldMapping.getDestination();
		
		validateConvertMethod(source, destination, errors, source.getConverterMethod());
		validateCallBackMethod(source, destination, errors, fieldMapping.getPostCallBack(), biDirectional);
		validateResolverMethod(source, destination, errors, source.getResolver());
		validateCollectionConvertMethod(source, destination, errors, source.getCollectionObjectConverter());
		

		do {
			validateFieldInfo(destination, biDirectional, false, errors);
			validateGetMethod(destination, errors);
			validateSetMethod(destination, errors);
			validateCollectionTypeInfo(destination, errors);
			validateConvertMethod(destination, source, errors, destination.getConverterMethod());			
			validateResolverMethod(destination, source, errors, destination.getResolver());
			validateCollectionConvertMethod(destination, source, errors, destination.getCollectionObjectConverter());
		} while ((destination = destination.getChildFieldInfo()) != null);

		return errors;
	}

	private void validateFieldInfo(FieldInfo fieldInfo, boolean biDirectional, boolean source, List<String> errors) {
		if (biDirectional) {
			if (fieldInfo.getGetMethod() == null || fieldInfo.getSetMethod() == null) {
				errors.add("Need to specify getter and setter method for field " + fieldInfo.getName() + " in " + fieldInfo.getClazz());
			}
		} else {
			if (fieldInfo.getGetMethod() == null && source) {
				errors.add("Need to specify getter method for field " + fieldInfo.getName() + " in " + fieldInfo.getClazz());
			}

			if (fieldInfo.getSetMethod() == null && !source) {
				errors.add("Need to specify setter method for field " + fieldInfo.getName() + " in " + fieldInfo.getClazz());
			}
		}

	}

	private void validateCollectionTypeInfo(FieldInfo fieldInfo, List<String> errors) {
		if (Collection.class.isAssignableFrom(fieldInfo.getFieldType())) {
			if (fieldInfo.getFieldType().isInterface()) {
				if (fieldInfo.getConcreteCollectionType() == null || fieldInfo.getConcreteCollectionType() == void.class) {
					errors.add("Need to specify concreteCollectionType for " + fieldInfo.getFieldType() + " for field " + fieldInfo.getName() + " in " + fieldInfo.getClazz());
				}
			}
			if (fieldInfo.getElementType() == null || fieldInfo.getElementType() == void.class) {
				errors.add("Need to specify elementType for field " + fieldInfo.getName() + " in class " + fieldInfo.getClazz());
			}

			if (fieldInfo.getIndexValue() != null) {
				if (!List.class.isAssignableFrom(fieldInfo.getFieldType())) {
					if (!List.class.isAssignableFrom(fieldInfo.getConcreteCollectionType())) {
						errors.add("Need to specify concreteCollectionType as instance of java.util.List " + fieldInfo.getFieldType() + " for field " + fieldInfo.getName() + " in " + fieldInfo.getClazz());
					}
				}
			}
		}

	}

	private void validateResolverMethod(FieldInfo source, FieldInfo destination, List<String> errors, String resolverMethod) {
		Class<?> sourcefieldType = getFieldType(source);
		if (resolverMethod != null && !resolverMethod.equals("")) {
			Class<?> destFieldType = getFieldType(destination);			
			
			try {
				if (resolverMethod.indexOf(".") != -1) {
					int lastIndexOf = resolverMethod.lastIndexOf(".");
					
					Method method = Class.forName(resolverMethod.substring(0, lastIndexOf)).getMethod(resolverMethod.substring(lastIndexOf + 1), destFieldType);
					if (!method.getReturnType().isAssignableFrom(sourcefieldType)) {
						errors.add("Invalid return type for " + resolverMethod + " for field " + source.getName() + " in " + source.getClazz());
					}

					if (!Modifier.isStatic(method.getModifiers())) {
						errors.add(resolverMethod + " must be static method" + " for field " + source.getName() + " in " + source.getClazz());
					}
				} else {
					errors.add("Invalid resolver " + resolverMethod + " for field " + source.getName() + " in " + source.getClazz());
				}
			} catch (ClassNotFoundException e) {
				errors.add("Invalid resolver Class in " + resolverMethod + " for field " + source.getName() + " in " + source.getClazz());
			} catch (NoSuchMethodException e) {
				errors.add("Invalid resolver method in " + resolverMethod + " for field " + source.getName() + " in " + source.getClazz());
			}
		} else {
			if (FieldHelper.isInterfaceOrAbstractClass(sourcefieldType) &&
					(source.getConverterMethod() == null || "".equals(source.getConverterMethod()))) {
				errors.add("Resolver method not found for field " + source.getName() + " in " + source.getClazz());
			}
		}
	}
	
	private void validateConvertMethod(FieldInfo source, FieldInfo destination, List<String> errors, String convertMethod) {
		if (convertMethod != null && !convertMethod.equals("")) {
			Class<?> fieldType = source.getFieldType();
			try {
				if (convertMethod.indexOf(".") != -1) {
					int lastIndexOf = convertMethod.lastIndexOf(".");
					Method method = Class.forName(convertMethod.substring(0, lastIndexOf)).getMethod(convertMethod.substring(lastIndexOf + 1), fieldType);
					if (!method.getReturnType().isAssignableFrom(destination.getFieldType())) {
						errors.add("Invalid return type for " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
					}

					if (!Modifier.isStatic(method.getModifiers())) {
						errors.add(convertMethod + " must be static method" + " for field " + source.getName() + " in " + source.getClazz());
					}
				} else {
					errors.add("Invalid converter " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
				}
			} catch (ClassNotFoundException e) {
				errors.add("Invalid converter Class in " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
			} catch (NoSuchMethodException e) {
				errors.add("Invalid converter method in " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
			}
		}
	}	
	
	private void validateCollectionConvertMethod(FieldInfo source, FieldInfo destination, List<String> errors, String convertMethod) {
		if (convertMethod != null && !convertMethod.equals("")) {
			Class<?> destFieldType = getFieldType(destination);
			Class<?> sourceFieldType = getFieldType(source);
			try {
				if (convertMethod.indexOf(".") != -1) {
					int lastIndexOf = convertMethod.lastIndexOf(".");
					Method method = Class.forName(convertMethod.substring(0, lastIndexOf)).getMethod(convertMethod.substring(lastIndexOf + 1), 
							sourceFieldType);
					
					if (!method.getReturnType().isAssignableFrom(destFieldType)) {
						errors.add("Invalid return type for " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
					}

					if (!Modifier.isStatic(method.getModifiers())) {
						errors.add(convertMethod + " must be static method" + " for field " + source.getName() + " in " + source.getClazz());
					}
				} else {
					errors.add("Invalid converter " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
				}
			} catch (ClassNotFoundException e) {
				errors.add("Invalid converter Class in " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
			} catch (NoSuchMethodException e) {
				errors.add("Invalid converter method in " + convertMethod + " for field " + source.getName() + " in " + source.getClazz());
			}
		}
	}	
	
	private void validateCallBackMethod(FieldInfo source, FieldInfo destination, List<String> errors, String callBackMethod,
			boolean bidirectional) {
		if (callBackMethod != null && !callBackMethod.equals("")) {
			Class<?> destFieldType = getFieldType(destination);
			Class<?> sourceFieldType = getFieldType(source);
			
			try {
				if (callBackMethod.indexOf(".") != -1) {
					int lastIndexOf = callBackMethod.lastIndexOf(".");
					Method method = Class.forName(callBackMethod.substring(0, lastIndexOf)).getMethod(callBackMethod.substring(lastIndexOf + 1), 
							sourceFieldType, destFieldType);
					if (!Modifier.isStatic(method.getModifiers())) {
						errors.add(callBackMethod + " must be static method" + " for field " + source.getName() + " in " + source.getClazz());
					}
					
					if (bidirectional) {
						method = Class.forName(callBackMethod.substring(0, lastIndexOf)).getMethod(callBackMethod.substring(lastIndexOf + 1), 
								destFieldType, sourceFieldType);
						if (!Modifier.isStatic(method.getModifiers())) {
							errors.add(callBackMethod + " must be static method" + " for field " + source.getName() + " in " + source.getClazz());
						}	
					}
				} else {
					errors.add("Invalid call back method " + callBackMethod + " for field " + source.getName() + " in " + source.getClazz());
				}
			} catch (ClassNotFoundException e) {
				errors.add("Invalid call back Class in " + callBackMethod + " for field " + source.getName() + " in " + source.getClazz());
			} catch (NoSuchMethodException e) {				
				errors.add("Invalid call back method " + callBackMethod + " for field " + source.getName() + " in " + source.getClazz());
			}
		}
	}
	
	private Class<?> getFieldType(FieldInfo fieldInfo) {
		if (Collection.class.isAssignableFrom(fieldInfo.getFieldType())
				|| Map.class.isAssignableFrom(fieldInfo.getFieldType())){
			return fieldInfo.getElementType();
		}
		
		if (fieldInfo.getFieldType().isArray()){
			return fieldInfo.getFieldType().getComponentType();
		}
		
		return fieldInfo.getFieldType();
	}

	private void validateGetMethod(FieldInfo fieldInfo, List<String> errors) {
		String getMethod = fieldInfo.getGetMethod();

		if (getMethod != null) {
			boolean found = false;
			for (Method method : FieldHelper.getMethods(fieldInfo.getClazz())) {
				String methodName = method.getName();

				if (methodName.equals(getMethod)) {
					if (fieldInfo.getFieldType() == method.getReturnType() && (method.getParameterTypes() == null || method.getParameterTypes().length == 0)) {
						found = true;
					}
				}
			}

			if (!found) {
				errors.add("Getter method " + getMethod + " not found for field " + fieldInfo.getName() + " in " + fieldInfo.getClazz());

			}
		}
	}

	private void validateSetMethod(FieldInfo fieldInfo, List<String> errors) {
		String setMethod = fieldInfo.getSetMethod();

		if (setMethod != null) {
			boolean found = false;
			for (Method method : FieldHelper.getMethods(fieldInfo.getClazz())) {
				String methodName = method.getName();

				if (methodName.equals(setMethod)) {
					if ((method.getParameterTypes() != null && method.getParameterTypes().length == 1 && fieldInfo.getFieldType() == method.getParameterTypes()[0])) {
						found = true;
					}
				}
			}

			if (!found) {
				errors.add("Setter method " + setMethod + " not found for field " + fieldInfo.getName() + " in " + fieldInfo.getClazz());

			}
		}
	}
}
